package com.djahva.image;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.awt.image.BufferedImage;

import javax.imageio.ImageIO;

public class ImageCache {

	private Map<String, CachedObject> cachedImages;
	private File cache; //basislocatie = Resin4.0.15(\cache)
	private int hitCount = 0;
	private int totalRequests = 0;
	private int totalTimeWin = 0;
	
	private int maxAge; //ms
	private int maxDiskSpace; //B

	public ImageCache(String cacheLocation, int cacheSize, int cacheAge) {
		cachedImages = new HashMap<String, CachedObject>();
		setMaxAge(cacheAge);
		setMaxDiskSpace(cacheSize);
		cache = new File(cacheLocation);
	}
	
	private void setMaxAge(int maxAge){
		if(maxAge < 0){
			this.maxAge = 120000; 
		} else {
			this.maxAge = maxAge;
		}
	}
	
	private void setMaxDiskSpace(int maxDisk){
		if(maxDisk < 0){
			this.maxDiskSpace = maxDisk;
		} else {
			this.maxDiskSpace = 20 * 1024 * 1024;
		}
	}

	public synchronized byte[] get(Map properties, String imageName) throws IOException {
		// image ophalen van cache
		totalRequests++;
		if (cachedImages.containsKey(imageName + properties.toString())) {
			long begin = System.currentTimeMillis();
			CachedObject co = cachedImages.get(imageName + properties.toString());
			co.objectAccessed();
			File f = co.getFile();
			
			FileInputStream fis = new FileInputStream(f);
			byte[] bytes = new byte[(int)f.length()];
			
			int offset = 0;
		    int numRead = 0;
		    while (offset < bytes.length && 
		    		(numRead=fis.read(bytes, offset, bytes.length-offset)) >= 0) {
		        offset += numRead;
		    }
			fis.close();
			hitCount++;
			long end = System.currentTimeMillis();
			totalTimeWin += co.getProcesTime() - (end-begin);
			return bytes;
		}
		return null;
	}
	
	//returns de grootte van de aangemaakte file
	public synchronized int store(BufferedImage image, String mime, Map properties, String imageName, long procesTime)
			throws IOException {
		if (!cachedImages.containsKey(imageName + properties.toString())) {
			try{
				// image opslaan op disk
				if(!cache.exists()){
					cache.mkdir();
				}
				String[] mimeSplit = mime.split("/");
				File outputFile = File.createTempFile("image", null, cache);
				outputFile.deleteOnExit();
				ImageIO.write(image, mimeSplit[1], outputFile);
				
				// image toevoegen aan map
				CachedObject co = new CachedObject(outputFile,System.currentTimeMillis(),procesTime);
				cachedImages.put(imageName + properties.toString(), co);
				//cache beperken in disk ruimte
				cleanUpByDiskSpace();
				return (int) outputFile.length();
			} catch(IOException e ){
				//logging toevoegen
				System.out.println(e);
			}
		}
		return 0;
	}

	public synchronized void cleanUp() {
		//cache volledig leeg maken
		Iterator<String> iterator = cachedImages.keySet().iterator();
		while(iterator.hasNext()) {
			File f = cachedImages.get(iterator.next()).getFile();
			f.delete();
		}
		cachedImages = new HashMap<String, CachedObject>();
	}
	
	public void cleanUpByTime(){
		// images verwijderen die te lang op disk staan
		ArrayList<CachedObject> filesToDelete = new ArrayList<CachedObject>();
		synchronized(this){
			long now = System.currentTimeMillis();
			Iterator<String> iterator = cachedImages.keySet().iterator();
			while(iterator.hasNext()) {
				String key = iterator.next();
				CachedObject co = cachedImages.get(key);
				if(co.getLastAccess() + maxAge < now){
					filesToDelete.add(co);
					cachedImages.remove(key);
				}
			}
		}
		
		for(CachedObject co : filesToDelete){
			co.deleteFile();
		}
	}
	
	private synchronized void cleanUpByDiskSpace(){
		if(getCacheDiskSpace() >= maxDiskSpace){
			//als teveel diskruimte gebruikt wordt oudste verwijderen
			//nog veel andere mogelijkheden mogelijk
			CachedObject oudste = null;
			String keyOudste = "";
			while(getCacheDiskSpace() >= maxDiskSpace){
				Iterator<String> iterator = cachedImages.keySet().iterator();
				oudste = null;
				while(iterator.hasNext()) {
					String key = iterator.next();
					CachedObject co = cachedImages.get(key);
					if(oudste == null || co.getLastAccess() < oudste.getLastAccess()){
						oudste = co;
						keyOudste = key;
					}
				}
				oudste.getFile().delete();
				cachedImages.remove(keyOudste);
			}
		}
	}
	
	//returns in html info over de cache
	public String info(){
		String output = "<div>";
		output += "<p>Total number of requests: " + totalRequests + "<br/>";
		output += "Total number of cache hits: " + hitCount + "<br/>";
		output += "Hitratio: " + hitRatio() + " %<br/><br/>";
		if(maxDiskSpace > 1024*1024){
			output += "Max diskspace cache: " + (double)maxDiskSpace/(1024*1024) + " MB<br/>";
		} else {
			output += "Max diskspace cache: " + (double)maxDiskSpace/1024 + " KB<br/>";
		}
		if(getCacheDiskSpace() > 1024 * 1024){
			output += "Diskspace used: " + String.format("%8.3f", (double)getCacheDiskSpace()/(1024 * 1024)) + " MB<br/><br/>";
		} else {
			output += "Diskspace used: " + String.format("%8.3f", (double)getCacheDiskSpace()/1024) + " KB<br/><br/>";
		}
		output += "Total time won by using cache: <b>" + totalTimeWin + " ms</b><br/><br/>";
		output += "Files in cache:<br/>";
		output += "<table><tr><th>disk space (B)</th><th>Access count</th><th>Age (s)</th><th>File</th></tr>";
		Iterator<String> iterator = cachedImages.keySet().iterator();
		while(iterator.hasNext()){
			String key = iterator.next();
			CachedObject co = cachedImages.get(key);
			File f = co.getFile();
			long now = System.currentTimeMillis();
			output += "<tr><td>" + f.length() + "</td><td>" + String.format("%3d", co.getAccessCount()) + "</td><td>" + String.format("%6.3f", (double)(now - co.getLastAccess())/1000) + "</td><td>" + f.getAbsoluteFile()  + "</td></tr>";
		}
		output += "</table>";
		return output += "</p></div>";
	}
	
	private String hitRatio(){
		if(totalRequests > 0){
			return String.format("%4.2f", ((double)hitCount/(double)totalRequests)*100);
		} else {
			return "-1";
		}
	}
	
	private int getCacheDiskSpace(){
		Iterator<String> iterator = cachedImages.keySet().iterator();
		int usedDiskSpace = 0;
		while(iterator.hasNext()){
			String key = iterator.next();
			File f = cachedImages.get(key).getFile();
			usedDiskSpace += f.length();
		}
		return usedDiskSpace;
	}
	
	public static void main(String[] args) {
		try{
			String mime = "image/jpeg";
			Map properties = new HashMap();
			properties.put("width", 800);
			File input = new File("C:\\Users\\Maarten\\Desktop\\wasp1.jpg");
			BufferedImage bi = ImageIO.read(input);
			File input2 = new File("C:\\Users\\Maarten\\Desktop\\wasp2.jpg");
			BufferedImage bi2 = ImageIO.read(input);
			ImageCache ic = new ImageCache("cache",1 * 1024 * 1024, 60000);
			ic.store(bi,mime,properties, "wasp1.jpg",System.currentTimeMillis());
			
			Map properties2 = new HashMap();
			properties2.put("width", 900);
			ic.store(bi2,mime,properties2, "wasp2.jpg",System.currentTimeMillis());
			
			Map properties3 = new HashMap();
			properties3.put("width", 1000);
			properties3.put("height", 1000);
			properties3.put("fliphorizontal", "true");
			ic.store(bi,mime,properties3, "wasp2.jpg",System.currentTimeMillis());
			
			System.out.println(ic.getCacheDiskSpace());
			
			byte[] bytes = ic.get(properties3,"wasp2.jpg");
			System.out.println(bytes.length);
			
			//ic.cleanUpByDiskSpace();
			ic.cleanUp();
		} catch(Exception e){
			System.out.println(e);
		}
	}
}
